/*******************************************************************************************
 * Copyright (c) 2006-2009 Alex Robson, Rob Simmons, Craig Israel, and Rachel Twyford
 *
 * Use of this software in any form requires that you have both 
 * read and agreed to the following terms:
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions: 
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * Neither the name of Nvigorate nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ****************************************************************************************/

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Xml.Linq;
using Nvigorate.Common;
using System.Linq;
using Nvigorate.Logging;
using Nvigorate.Xml;

namespace Nvigorate.Data.Logging
{
    public class DatabaseLogAdapter : ILogAdapter
    {
        private Severities _errorLevel = Severities.Low;
        private string _connectionString = "";
        private DataInterface _database;
        private LogTableSchema _schema;
        internal static bool _hasSchemaAccess = true;
        internal static bool _schemaExists = false;
        internal static bool _schemaCheckInProgress = false;

        public Severities ErrorLevel
        {
            get { return _errorLevel; }
            set { _errorLevel = value; }
        }

        #region ILogAdapter Members
        public bool LoadConfiguration(XElement xml)
        {
            string connectionName = xml.GetChildElementValue("connectionName") ?? "";
            string errorLevel = xml.GetChildElementValue("errorLevel") ?? "2";
            bool checkForSchema = bool.Parse(xml.GetChildElementValue("checkSchema") ?? "false");

            //Connection name was not provided
            if(connectionName == "")
                return false;

            if (errorLevel != "")
                _errorLevel = (Severities) Enum.Parse(typeof (Severities), errorLevel);

            _connectionString = ConfigurationManager.ConnectionStrings[connectionName].ConnectionString;
            string provider = ConfigurationManager.ConnectionStrings[connectionName].ProviderName ?? "";

            _database = new DataInterface(provider, _connectionString);
            
            _schema = new LogTableSchema();
            if(xml.Descendants("schema").FirstOrDefault() != null)
            {
                _schema.GetSchema(xml.Descendants("schema").First());
            }

            if (_hasSchemaAccess && !_schemaCheckInProgress && checkForSchema)
            {
                //For some reason against arcturus this ran constantly until connection timeout : (
                if (!_schema.CheckForTable(_database))
                {
                    _schema.CreateTable(_database);
                }
            }
            else
            {
                _hasSchemaAccess = false;
                _schemaExists = true;
            }

            return true;
        }

        public void LogError(string machineName, string user, string application, string assembly, string className, string method, Severities severity,
                             string exceptionText, string errorMessage, params string[] messageParameters)
        {
            if(severity < _errorLevel || !_schemaExists)
                return;
            var parameters = _schema.GetParameters(_database.Provider,
                                                    machineName, 
                                                    user,
                                                    application,
                                                    assembly,
                                                    className,
                                                    method,
                                                    severity,
                                                    exceptionText,
                                                    errorMessage,
                                                    messageParameters);    
            _database.ExecuteCommand(_schema.GetInsertText(_database.Provider), false, parameters);
        }

        #endregion
    }

    internal class LogTableSchema
    {
        private string _tableName = "errorLog";
        private string _logEntry = "logEntry";
        private string _machine = "machine";
        private string _user = "userName";
        private string _application = "application";
        private string _assembly = "assembly";
        private string _className = "class";
        private string _method = "method";
        private string _exception = "exception";
        private string _errorMessage = "message";
        private string _severity = "severity";
        private string _dateTime = "occurred";

        private const string CREATE_TABLE_SQL =
                @"
CREATE TABLE [dbo].{0}
(
    {1} BIGINT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    {2} VARCHAR(256) NULL,
    {3} VARCHAR(256) NULL,
    {4} VARCHAR(512) NULL,
    {5} VARCHAR(512) NULL,
    {6} VARCHAR(256) NULL,
    {7} TEXT,
    {8} TEXT,
    {9} DATETIME,
    {10} INT,
    {11} VARCHAR(256)
)
";

        public void GetSchema(XElement element)
        {
            _tableName = element.GetAttributeValue("table") ?? _tableName;
            _tableName = element.GetAttributeValue("machine") ?? _machine;
            _tableName = element.GetAttributeValue("userName") ?? _user;
            _application = element.GetAttributeValue("appColumn") ?? _application;
            _assembly = element.GetAttributeValue("assemblyColumn") ?? _assembly;
            _className = element.GetAttributeValue("classColumn") ?? _className;
            _method = element.GetAttributeValue("methodColumn") ?? _method;
            _exception = element.GetAttributeValue("exceptionColumn") ?? _exception;
            _errorMessage = element.GetAttributeValue("messageColumn") ?? _errorMessage;
            _severity = element.GetAttributeValue("severityColumn") ?? _severity;
            _dateTime = element.GetAttributeValue("timeColumn") ?? _dateTime;
        }

        public string GetInsertText(IDatabaseProvider provider)
        {
            string columnList = DelimitedBuilder.Construct(new[]
                                                               {
                                                                   _application,
                                                                   _machine,
                                                                   _assembly,
                                                                   _className,
                                                                   _method,
                                                                   _exception,
                                                                   _errorMessage,
                                                                   _severity,
                                                                   _dateTime
                                                               }, ", ");

            string paramNameList = DelimitedBuilder.Construct(new[]
                                                               {
                                                                   _application,
                                                                   _machine,
                                                                   _assembly,
                                                                   _className,
                                                                   _method,
                                                                   _exception,
                                                                   _errorMessage,
                                                                   _severity,
                                                                   _dateTime
                                                               }, ", ", provider.ParameterPrefix, "");

            return "";
            //return string.Format(provider.InsertSelectiveFormatString,
            //                     _tableName,
            //                     columnList,
            //                     paramNameList);
        }
    
        public List<IDbDataParameter> GetParameters(IDatabaseProvider provider, string machine, string user, string application, string assembly, string className, string method, Severities severity,
                             string exceptionText, string errorMessage, params string[] messageParameters)
        {
            List<IDbDataParameter> parameters = new List<IDbDataParameter>();

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _application,
                                                 application.Replace("'", "`"),
                                                 typeof (string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _machine,
                                                 machine.Replace("'", "`"),
                                                 typeof (string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _user,
                                     user.Replace("'", "`"),
                                     typeof(string),
                                     ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _assembly,
                                                 assembly.Replace("'", "`"),
                                                 typeof(string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _className,
                                                 className.Replace("'", "`"),
                                                 typeof(string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _method,
                                                 method.Replace("'", "`"),
                                                 typeof(string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _exception,
                                                 exceptionText.Replace("'", "`"),
                                                 typeof(string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _errorMessage,
                                                 string.Format(errorMessage, messageParameters).Replace("'", "`"),
                                                 typeof(string),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _severity,
                                                 (int) severity,
                                                 typeof(int),
                                                 ParameterDirection.Input));

            parameters.Add(provider.GetParameter(provider.ParameterPrefix + _dateTime,
                                                 DateTime.Now,
                                                 typeof(DateTime),
                                                 ParameterDirection.Input));

            return parameters;
        }

        public bool CheckForTable(DataInterface dataInterface)
        {
            DatabaseLogAdapter._schemaCheckInProgress = true;

            string query = "select [name] as TableName from sys.tables where [name] = @table";


            try
            {
                DataTable table = dataInterface.GetTable(
                    query, 
                    "result",
                    new List<IDbDataParameter>()
                        {
                            new SqlParameter("@table", _tableName)
                        }
                    );
                return table.Rows.Count > 0;
            }
            catch (Exception e)
            {
                DatabaseLogAdapter._hasSchemaAccess = false;
                return false;
            }
        }

        public void CreateTable(DataInterface dataInterface)
        {
            if(!DatabaseLogAdapter._hasSchemaAccess)
                return;

            try
            {
                string commandSql = string.Format(CREATE_TABLE_SQL,
                                                  _tableName,
                                                  _logEntry,
                                                  _application,
                                                  _assembly,
                                                  _className,
                                                  _method,
                                                  _exception,
                                                  _errorMessage,
                                                  _dateTime,
                                                  _severity,
                                                  _machine
                    );
                dataInterface.ExecuteCommand(commandSql, false);
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}